#![cfg_attr(feature = "axstd", no_std)]
#![cfg_attr(feature = "axstd", no_main)]

#[cfg(feature = "axstd")]
extern crate axstd as std;

use net_cmd::{
    ETHERNET_USAGE_LINES, EthernetCommand, EthernetParseError, EthernetResponse, EthernetStatus,
    InterfaceMode, TFTP_USAGE_LINES, TftpCommand, TftpError, TftpOptions, TftpParseError,
    TftpResponse,
};

#[cfg_attr(feature = "axstd", unsafe(no_mangle))]
fn main() {
    #[cfg(feature = "axstd")]
    run();
    #[cfg(not(feature = "axstd"))]
    run();
}

fn run() {
    let mut args = std::env::args();
    let _program = args.next();
    let Some(command) = args.next() else {
        print_usage();
        return;
    };

    match command.as_str() {
        "ethernet" => handle_ethernet(args.collect()),
        "tftp" => handle_tftp(args.collect()),
        "help" => print_usage(),
        other => {
            println!("net-tool: unknown command '{other}'");
            print_usage();
        }
    }
}

fn handle_ethernet(args: Vec<String>) {
    if args.is_empty() {
        print_ethernet_usage();
        return;
    }

    let subcmd = &args[0];
    match parse_ethernet(subcmd, &args[1..]) {
        Ok(command) => match net_cmd::ethernet::execute(command) {
            Ok(response) => print_ethernet_response(response),
            Err(err) => println!("ethernet: driver error: {err:?}"),
        },
        Err(err) => {
            println!("ethernet: {err}");
            print_ethernet_usage();
        }
    }
}

fn handle_tftp(args: Vec<String>) {
    if args.is_empty() {
        print_tftp_usage();
        return;
    }

    let subcmd = &args[0];
    match parse_tftp(subcmd, &args[1..]) {
        Ok(command) => match net_cmd::tftp::execute(command, TftpOptions::default()) {
            Ok(response) => print_tftp_response(response),
            Err(err) => print_tftp_error(err),
        },
        Err(err) => {
            println!("tftp: {err}");
            print_tftp_usage();
        }
    }
}

fn parse_ethernet(
    subcmd: &str,
    rest: &[String],
) -> Result<EthernetCommand, EthernetParseError<'_>> {
    net_cmd::ethernet::parse_command(subcmd, rest.iter().map(|s| s.as_str()))
}

fn parse_tftp(subcmd: &str, rest: &[String]) -> Result<TftpCommand<'_>, TftpParseError<'_>> {
    net_cmd::tftp::parse_command(subcmd, rest.iter().map(|s| s.as_str()))
}

fn print_ethernet_response(response: EthernetResponse) {
    match response {
        EthernetResponse::Init => println!("ethernet: default configuration applied"),
        EthernetResponse::Status(status) => display_status(status),
        EthernetResponse::Speed { id, speed } => {
            println!(
                "ethernet: GMAC{} speed set to {} Mbps",
                gmac_index(id),
                match speed {
                    axplat_aarch64_opi5p::ethernet::GmacLinkSpeed::Speed10 => 10,
                    axplat_aarch64_opi5p::ethernet::GmacLinkSpeed::Speed100 => 100,
                    axplat_aarch64_opi5p::ethernet::GmacLinkSpeed::Speed1000 => 1000,
                }
            );
        }
        EthernetResponse::Delay { id, delay } => {
            println!(
                "ethernet: GMAC{} delay updated tx=0x{:02x} rx=0x{:02x}",
                gmac_index(id),
                delay.tx,
                delay.rx
            );
        }
        EthernetResponse::Help => print_ethernet_usage(),
    }
}

fn display_status(status: EthernetStatus) {
    println!(
        "ethernet: PHP_GRF_GMAC_CON0=0x{:08x} PHP_GRF_CLK_CON1=0x{:08x}",
        status.phy_sel, status.clk_ctrl
    );
    println!("ethernet: SYS_GRF_GMAC_CON7 =0x{:08x}", status.delay_enable);
    for mac in status.macs.iter() {
        println!("GMAC{}:", gmac_index(mac.id));
        println!("  interface   : {}", interface_name(mac.interface));
        println!(
            "  delays      : tx=0x{:02x} ({}) rx=0x{:02x} ({})",
            mac.tx_delay,
            if mac.tx_delay_enabled {
                "enabled"
            } else {
                "disabled"
            },
            mac.rx_delay,
            if mac.rx_delay_enabled {
                "enabled"
            } else {
                "disabled"
            }
        );
        println!(
            "  link speed  : {}",
            mac.negotiated_speed.map(speed_name).unwrap_or("unknown")
        );
    }
}

fn print_tftp_response(response: TftpResponse) {
    match response {
        TftpResponse::Help => print_tftp_usage(),
        TftpResponse::Get {
            server,
            remote,
            local,
            bytes,
            blocks,
        } => {
            println!(
                "tftp: downloaded {} bytes ({blocks} blocks) from {server}::{remote} -> {local}",
                bytes,
            );
        }
        TftpResponse::Put {
            server,
            local,
            remote,
            bytes,
            blocks,
        } => {
            println!(
                "tftp: uploaded {} bytes ({blocks} blocks) from {local} -> {server}::{remote}",
                bytes,
            );
        }
    }
}

fn print_tftp_error(err: TftpError) {
    match err {
        TftpError::Io(io_err) => println!("tftp: io error: {io_err}"),
        TftpError::Protocol(msg) => println!("tftp: {msg}"),
        TftpError::ProtocolMessage(msg) => println!("tftp: {msg}"),
    }
}

fn print_usage() {
    println!("net-tool commands:");
    println!("  ethernet - manage RK3588 GMAC configuration");
    println!("  tftp     - transfer files via TFTP");
    println!("  help     - show this message");
}

fn print_ethernet_usage() {
    for line in ETHERNET_USAGE_LINES {
        println!("{line}");
    }
}

fn print_tftp_usage() {
    for line in TFTP_USAGE_LINES {
        println!("{line}");
    }
}

fn interface_name(mode: InterfaceMode) -> &'static str {
    match mode {
        InterfaceMode::Rgmii => "RGMII",
        InterfaceMode::Rmii => "RMII",
        InterfaceMode::Unknown => "unknown",
    }
}

fn speed_name(speed: axplat_aarch64_opi5p::ethernet::GmacLinkSpeed) -> &'static str {
    match speed {
        axplat_aarch64_opi5p::ethernet::GmacLinkSpeed::Speed10 => "10 Mbps",
        axplat_aarch64_opi5p::ethernet::GmacLinkSpeed::Speed100 => "100 Mbps",
        axplat_aarch64_opi5p::ethernet::GmacLinkSpeed::Speed1000 => "1000 Mbps",
    }
}

const fn gmac_index(id: axplat_aarch64_opi5p::ethernet::GmacId) -> usize {
    match id {
        axplat_aarch64_opi5p::ethernet::GmacId::Gmac0 => 0,
        axplat_aarch64_opi5p::ethernet::GmacId::Gmac1 => 1,
    }
}
